#include <nxos.h>

NX_PRIVATE NX_Spin testSpinLock;

NX_PRIVATE int threadCounter[2] = {0, 0};
NX_PRIVATE int threadCounterSum = 0;

NX_PRIVATE NX_U32 ThreadEntry0(void *arg)
{
    NX_ThreadSleep(1000);
    while (threadCounter[0] < (3000 / (50 * 2)))
    {
        NX_SpinLock(&testSpinLock);
        NX_Printf("Thread 0 get lock\n");
        NX_ThreadSleep(50);
        NX_SpinUnlock(&testSpinLock);
        threadCounter[0]++;
        NX_ThreadSleep(50);
    }
    NX_Printf("Thread 0 exit\n");
    return 0;
}

NX_PRIVATE NX_U32 ThreadEntry1(void *arg)
{
    NX_ThreadSleep(1000);
    while (threadCounter[1] < (3000 / (100 * 2)))
    {
        NX_SpinLock(&testSpinLock);
        NX_Printf("Thread 1 get lock\n");
        NX_ThreadSleep(100);
        NX_SpinUnlock(&testSpinLock);
        threadCounter[1]++;
        NX_ThreadSleep(100);
    }
    NX_Printf("Thread 1 exit\n");

    return 0;
}

NX_PRIVATE NX_U32 ThreadEntry2(void *arg)
{
    NX_ThreadSleep(1000);
    while (threadCounter[0] < 0x1000000)
    {
        NX_SpinLock(&testSpinLock);
        threadCounterSum++;
        if ((threadCounter[0] % 0x100000) == 0)
        {
            NX_Printf("Thread 2 get lock\n");
        }
        NX_SpinUnlock(&testSpinLock);
        threadCounter[0]++;
    }
    NX_Printf("Thread 2 exit\n");
    return 0;
}

NX_PRIVATE NX_U32 ThreadEntry3(void *arg)
{
    NX_ThreadSleep(1000);
    while (threadCounter[1] < 0x1000000)
    {
        NX_SpinLock(&testSpinLock);
        threadCounterSum++;
        if ((threadCounter[1] % 0x100000) == 0)
        {
            NX_Printf("Thread 3 get lock\n");
        }
        NX_SpinUnlock(&testSpinLock);
        threadCounter[1]++;
    }
    NX_Printf("Thread 3 exit\n");

    return 0;
}

NX_Error TEST_Spin(char *cmdline)
{
    NX_SpinInit(&testSpinLock);

    NX_Printf("!!! this test may taken 15s !!!\n");

    NX_Printf("ready lock spin...\n");
    NX_SpinLock(&testSpinLock);
    NX_Printf("success lock spin... is locked:%d\n", NX_SpinIsLocked(&testSpinLock));
    NX_SpinUnlock(&testSpinLock);
    NX_Printf("done unlock spin...\n");
    
    NX_Printf("try lock spin...\n");
    NX_SpinTryLock(&testSpinLock);
    NX_Printf("success lock spin... is locked:%d\n", NX_SpinIsLocked(&testSpinLock));
    NX_Printf("try lock spin again... with state:%d\n", NX_SpinTryLock(&testSpinLock));
    NX_SpinUnlock(&testSpinLock);
    NX_Printf("done unlock spin...\n");

    NX_SpinInit(&testSpinLock);

    /* test in multi-thread env */
    NX_Solt t0 = NX_ThreadCreate(NX_NULL, ThreadEntry0, NX_NULL, 0);
    NX_Solt t1 = NX_ThreadCreate(NX_NULL, ThreadEntry1, NX_NULL, 0);
    
    if (t0 == NX_SOLT_INVALID_VALUE || t1 == NX_SOLT_INVALID_VALUE)
    {
        NX_Printf("create thread failed!\n");
    }

    NX_ThreadWait(t0, NX_NULL);
    NX_ThreadWait(t1, NX_NULL);

    threadCounter[0] = 0;
    threadCounter[1] = 0;

    NX_Solt t2 = NX_ThreadCreate(NX_NULL, ThreadEntry2, NX_NULL, 0);
    NX_Solt t3 = NX_ThreadCreate(NX_NULL, ThreadEntry3, NX_NULL, 0);
    
    if (t2 == NX_SOLT_INVALID_VALUE || t2 == NX_SOLT_INVALID_VALUE)
    {
        NX_Printf("create thread failed!\n");
    }

    NX_ThreadWait(t2, NX_NULL);
    NX_ThreadWait(t3, NX_NULL);

    if (threadCounterSum == 0x1000000 * 2)
    {
        NX_Printf("test thread counter success!\n");
    }
    else
    {
        NX_Printf("test thread counter failed!\n");
    }

    return NX_EOK;
}
